package org.threads.biginteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Thread-safe lock-free representation of sequence [1, 2, 4, 8, 16, ...]
 * that is the power of two elements.
 */
public class PowerOfTwo {

    private static final Logger LOGGER = LoggerFactory.getLogger(PowerOfTwo.class);

    static PowerOfTwo Instance = new PowerOfTwo();

    private final BigInteger two = new BigInteger("2");
    private final AtomicReference<BigInteger> valueHolder = new AtomicReference<>();

    /**
     * Common constructor. Initiates the value with 1.
     */
    private PowerOfTwo() {
        valueHolder.set(new BigInteger("1"));
    }

    /**
     * Returns the next element of sequence [1, 2, 4, 8, 16, ...]
     *
     * @return the next element in {@code BigInteger} representation
     */
    public BigInteger next() {

        BigInteger current = valueHolder.get();
        while (!valueHolder.compareAndSet(current, current.multiply(two))) {
            current = valueHolder.get();
        }
        return valueHolder.get();
    }

    /**
     * Program's entry point. Is used for testing
     * {@code PowerOfTwo} class.
     *
     * @param args
     */
    public static void main(String[] args) {

        final long delay = 50;
        final int threadsCount = 50;
        final int procCount = 30;

        ExecutorService executors = Executors.newFixedThreadPool(5);
        List<Callable<Collection<BigInteger>>> taskList = new LinkedList<>();

        for (int i = 0; i < threadsCount; i++) {

            taskList.add(new Callable<Collection<BigInteger>>() {
                @Override
                public Collection<BigInteger> call() {

                    List<BigInteger> list = new LinkedList<>();
                    int i = procCount;

                    while (--i > 0) {

                        BigInteger val = Instance.next();
                        list.add(val);

                        LOGGER.info(Thread.currentThread().getName() + ": " + val);

                        try {
                            Thread.sleep(delay);
                        } catch (InterruptedException e) {
                            LOGGER.error(e.toString());
                        }
                    }

                    return list;
                }
            });

        }

        Set<BigInteger> resultiveSet = new TreeSet<>();
        List<Future<Collection<BigInteger>>> results;

        try {

            results = executors.invokeAll(taskList);

            //testing resultive set on duplicates
            for (Future<Collection<BigInteger>> result : results) {
                for (BigInteger o : result.get()) {
                    if (!resultiveSet.add(o)) {
                        LOGGER.error(o + " is DUPLICATE!");
                    }
                }
            }

            //testing missed elements
            BigInteger previousVal = BigInteger.ONE;
            BigInteger two = new BigInteger("2");
            for (BigInteger val : resultiveSet) {

                BigInteger mustBe = val.divide(two);
                if (!previousVal.equals(mustBe)) {
                    LOGGER.error(mustBe + " is MISSED!");
                }
                previousVal = val;
            }


        } catch (InterruptedException | ExecutionException e) {
            LOGGER.error("", e);
        } finally {
            executors.shutdown();
        }
    }

}
